package org.apache.lucene.ocean;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.logging.Logger;

import org.apache.lucene.index.IndexCommit;
import org.apache.lucene.index.IndexReader;
import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.index.OceanSegmentReader;
import org.apache.lucene.index.SerialMergeScheduler;
import org.apache.lucene.ocean.SnapshotInfo.IndexInfo;
import org.apache.lucene.ocean.util.Util;
import org.apache.lucene.store.Directory;
import org.apache.lucene.store.FSDirectory;
import org.apache.lucene.store.RAMDirectory;

/**
 * On disk index.  Only deletes are allowed to occur to the index. 
 * There is a unique IndexReader per snapshot.
 *
 */
public class DiskIndex extends DirectoryIndex {
	public static Logger log = Logger.getLogger(DiskIndex.class.getName());
	private final Directory directory;
	//private IndexInfo indexInfo;
  
	public DiskIndex(IndexID id, Directory directory, TransactionSystem system) throws Exception, IndexException, IOException {
    super(id, system);
    //assert segmentGeneration != null;
    this.directory = directory;
    if (directory.fileExists("writing.index")) {
      throw new IndexNeverCompletedCopyException("index never completed copying");
    }
    if (IndexWriter.isLocked(directory)) {
      LOG.info("directory: "+directory+" locked.  being unlocked");
      IndexWriter.unlock(directory);
    }
    initialIndexReader = IndexReader.open(directory, indexDeletionPolicy);
    long readerGeneration = initialIndexReader.getIndexCommit().getGeneration();
    //indexInfo = loadIndexInfo();
    //createNewSnapshot(snapshotId, false, initialIndexReader);
  }
	
	// load existing index
	public DiskIndex(IndexID id, Directory directory, Long snapshotId, IndexInfo indexInfo, TransactionSystem system) throws Exception, IndexException, IOException {
		super(id, system);
		if (indexInfo.segmentGeneration == null) {
		  throw new IndexException(id+" indexInfo.segmentGeneration null");
		}
		assert indexInfo.segmentGeneration != null;
		this.directory = directory;
		this.mergedfromIndexIds.addAll(mergedfromIndexIds);
		this.checkpointSnapshotIds.add(indexInfo.checkpointId);
		this.lastAppliedSnapshotIds.add(indexInfo.lastAppliedId);
		if (directory.fileExists("writing.index")) {
			throw new IndexNeverCompletedCopyException("index never completed copying");
		}
		if (IndexWriter.isLocked(directory)) {
		  LOG.info("directory: "+directory+" locked.  being unlocked");
		  IndexWriter.unlock(directory);
		}
		initialIndexReader = IndexReader.open(directory, indexDeletionPolicy);
		long readerGeneration = initialIndexReader.getIndexCommit().getGeneration();
		assert indexInfo.segmentGeneration.longValue() == readerGeneration;
		//indexInfo = loadIndexInfo();
		// TODO: delete generations above the one known in indexinfo
		Iterator<Map.Entry<Long,IndexCommit>> commitPointIterator = indexDeletionPolicy.commitPoints.entrySet().iterator();
		while (commitPointIterator.hasNext()) {
		  Map.Entry<Long,IndexCommit> entry = commitPointIterator.next();
		  IndexCommit indexCommit = entry.getValue();
		  if (indexCommit.getGeneration() > indexInfo.getSegmentGeneration().longValue()) {
		    LOG.info(((FSDirectory)directory).getFile().getName()+" deleting generation: "+indexCommit.getGeneration());
		  }
		}
		createNewSnapshot(snapshotId, false, initialIndexReader);
	}
	
	// merge indexes creating new index
	public DiskIndex(IndexID id, Directory directory, List<? extends IndexSnapshot> indexSnapshots, TransactionSystem system) throws Exception, IOException {
		super(id, system);
		this.directory = directory;
		// TODO: capture exceptions here and throw exception with indexsnapshots info
		Util.touchFile("writing.index", directory);
		IndexReader[] indexReaders = getIndexReaders(indexSnapshots);
		// create in ram first, is faster than copying to disk due to less hard disk head movement
		int diskIndexRAMDirectoryBufferSize = system.getConfig().diskIndexRAMDirectoryBufferSize;
		RAMDirectory ramDirectory = new RAMDirectory(diskIndexRAMDirectoryBufferSize);
		IndexWriter indexWriter = new IndexWriter(ramDirectory, false, system.getDefaultAnalyzer(), true);
		indexWriter.setMaxBufferedDocs(Integer.MAX_VALUE);
		indexWriter.setRAMBufferSizeMB(IndexWriter.DISABLE_AUTO_FLUSH);
		indexWriter.setMergeScheduler(new SerialMergeScheduler());
		indexWriter.setUseCompoundFile(true);
		indexWriter.addIndexes(indexReaders);
		indexWriter.close();
		
		Util.copy(ramDirectory, directory); // copy out 1 megabyte at a time
		
		//indexInfo = new IndexInfo();
		//indexInfo.setMaxDocumentID(maxDocumentId);
		//indexInfo.setMaxSnapshotID(maxSnapshotId);
		//saveIndexInfo(indexInfo);
		directory.deleteFile("writing.index");
		addMergedFromIndexIds(indexSnapshots);
		initialIndexReader = IndexReader.open(directory, indexDeletionPolicy);
	}
  
	public Directory getDirectory() {
		return directory;
	}
  /**
	private IndexInfo loadIndexInfo() throws Exception {
		String xml = Util.getString("indexinfo.xml", directory);
		Element element = XMLUtil.parseElement(xml);
		return new IndexInfo(element);
	}

	private void saveIndexInfo(IndexInfo indexInfo) throws Exception {
		Element element = indexInfo.toElement();
		String xml = XMLUtil.outputElement(element);
		Util.save(xml, "indexinfo.xml", directory);
	}

	public static class IndexInfo implements CElement {
		private Long maxSnapshotId;
		private Long maxDocumentId;

		public IndexInfo() {
		}

		public Long getMaxSnapshotID() {
			return maxSnapshotId;
		}

		public void setMaxSnapshotID(Long maxSnapshotId) {
			this.maxSnapshotId = maxSnapshotId;
		}

		public Long getMaxDocumentID() {
			return maxDocumentId;
		}

		public void setMaxDocumentID(Long maxDocumentId) {
			this.maxDocumentId = maxDocumentId;
		}

		public IndexInfo(Element element) throws Exception {
		  maxSnapshotId = XMLUtil.getAttributeLong("maxSnapshotId", element);
		  maxDocumentId = XMLUtil.getAttributeLong("maxDocumentId", element);
		}

		public Element toElement() throws Exception {
		  Element element = new Element("indexinfo");
		  XMLUtil.setAttribute("maxSnapshotId", maxSnapshotId, element);
		  XMLUtil.setAttribute("maxDocumentId", maxDocumentId, element);
		  return element;
		}
	}
  **/
	public boolean hasTooManyDeletedDocs(double percent) {
	  assert percent <= 1.0;
		DirectoryIndexSnapshot indexSnapshot = getLatestIndexSnapshot();
		if (indexSnapshot != null) {
			IndexReader indexReader = indexSnapshot.getIndexReader();
			int maxDoc = indexReader.maxDoc();
			int deletedDocs = maxDoc - indexReader.numDocs();
			if (deletedDocs > (maxDoc * percent))
				return true;
		}
		return false;
	}

	public class DiskIndexSnapshot extends DirectoryIndexSnapshot {
		private Collection<String> indexReaderFileNames;

		public DiskIndexSnapshot(Long snapshotID, OceanSegmentReader indexReader, Collection<String> indexReaderFileNames) throws IOException {
			super(snapshotID, indexReader);
			this.indexReaderFileNames = indexReaderFileNames;
		}

		//public Long getMaxSnapshotId() throws IOException {
		//	return indexInfo.getMaxSnapshotID();
		//}

		//public Long getMaxDocumentId() throws IOException {
		//	return indexInfo.getMaxDocumentID();
		//}
    
		public void delete() throws Exception {
			super.delete();
			deleteFiles();
		}
		
		public boolean hasRef() throws Exception {
			return getSystem().getSnapshots().contains(snapshotId);
		}

		public void deleteFiles() throws IOException {
		}

		public List<String> getFiles() throws Exception {
			List<String> files = new ArrayList<String>();
			for (String fileName : indexReaderFileNames) {
				files.add(fileName);
			}
			return files;
		}
	}
  
	protected void onCommit() throws Exception {
	}

	protected DiskIndexSnapshot doCreateNewSnapshot(Long snapshotId, IndexReader newIndexReader) throws IOException {
	  IndexCommit indexCommit = newIndexReader.getIndexCommit();
		Collection<String> fileNames = indexCommit.getFileNames();
		DiskIndexSnapshot diskIndexSnapshot = new DiskIndexSnapshot(snapshotId, (OceanSegmentReader)newIndexReader, fileNames);
		registerSnapshot(diskIndexSnapshot);
		return diskIndexSnapshot;
	}
}
